
// ===============================================================================================================
// -*- C++ -*-
//
// Texture2D.cpp - 2D Texture creation and management.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <GfxLib.hpp>
#include <map>

// == Local data/code ==

// Invalid val for a GL texture id
#define BAD_TEXTURE_ID ((GLuint)-1)

typedef std::map<const std::string, GLuint> textureCacheMap;
static textureCacheMap textureCache;
static GLuint * textureIds;
static GLuint usedTextures;
static GLuint nTextures;

namespace GfxLib {

void InitTextures(unsigned int maxTextures)
{
	CleanupTextures();

	textureIds = reinterpret_cast<GLuint *>(malloc(sizeof(GLuint) * maxTextures));

	if (textureIds == 0)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Out of memory for texture Ids...");
		return;
	}

	usedTextures = 0;
	nTextures = maxTextures;

	glGenTextures(nTextures, textureIds);
	assert(glGetError() == GL_NO_ERROR);
}

bool CreateGLTexture2DFromFile(const char * fileName, GLint wrapMode, GLint minFilter, GLint magFilter, int & w, int & h, GLuint & id)
{
	assert(textureIds != 0 && nTextures > 0 && fileName != 0); // Just in case...

	if (usedTextures == nTextures)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "No more textures!");
		id = BAD_TEXTURE_ID;
		return (false);
	}

	textureCacheMap::const_iterator it = textureCache.find(fileName);

	if (it != textureCache.end())
	{
		// Texture already created:
		id = (*it).second;
		return (true);
	}
	else
	{
		// Create new texture:
		GLenum glTexFmt;
		unsigned char * pixels = 0;
		int width, height, bytesPerPix;

		const char * ext = strrchr(fileName, '.');

		if ((ext != 0) && (ext[1] != 0))
		{
			// Case-insensitive compare:
			if (stricmp(ext, ".bmp") == 0)
			{
				pixels = LoadBitmapImage(fileName, width, height, bytesPerPix);
				glTexFmt = ((bytesPerPix == 4) ? GL_BGRA_EXT : GL_BGR_EXT); // Bitmaps are stored in BGRA order
			}
			else if (stricmp(ext, ".jpg") == 0)
			{
				pixels = LoadJpegImage(fileName, width, height, bytesPerPix);
				glTexFmt = ((bytesPerPix == 4) ? GL_RGBA : GL_RGB); // JPEGs are stored in RGBA order
			}
			else if (stricmp(ext, ".tga") == 0)
			{
				pixels = LoadTgaImage(fileName, width, height, bytesPerPix);
				glTexFmt = ((bytesPerPix == 4) ? GL_BGRA_EXT : GL_BGR_EXT);
			}
			else
			{
				CommLib::DbgPrintf(PRINT_ERROR, "Invalid image file extension! Must be either .bmp or .tga or .jpg");
				id = BAD_TEXTURE_ID;
				return (false);
			}
		}
		else
		{
			CommLib::DbgPrintf(PRINT_ERROR, "File without extension -- (Unknown type) -- File Name: %s", fileName);
			id = BAD_TEXTURE_ID;
			return (false);
		}

		if (pixels != 0)
		{
			GLuint newTex = textureIds[usedTextures];
			textureCache.insert(textureCacheMap::value_type(fileName, newTex));
			++usedTextures;

			glBindTexture(GL_TEXTURE_2D, newTex);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);

			if (GLEW_SGIS_generate_mipmap) // Hardware mipmap generation:
			{
				glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
				glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);

				glTexImage2D(GL_TEXTURE_2D, 0, bytesPerPix, width, height, 0, glTexFmt, GL_UNSIGNED_BYTE, pixels);
			}
			else // No hardware mipmap generation available, use software:
			{
				gluBuild2DMipmaps(GL_TEXTURE_2D, bytesPerPix, width, height, glTexFmt, GL_UNSIGNED_BYTE, pixels);
			}

			free(pixels);

			CommLib::DbgPrintf(PRINT_MSG, "Created a %dx%d pixels texture", width, height);

			if (glGetError() != GL_NO_ERROR)
			{
				CommLib::DbgPrintf(PRINT_WARN, "OpenGL reported errors creating a 2D texture...");
			}

			w = width;
			h = height;
			id = newTex;

			return (true);
		}
		else
		{
			id = BAD_TEXTURE_ID;
			return (false);
		}
	}
}

bool CreateGLTexture2DFromMemory(const unsigned char * pixels, const char * textureName, GLint wrapMode,
								 GLint minFilter, GLint magFilter, GLenum glTexFmt, int w, int h, int bytesPerPix, GLuint & id)
{
	assert(textureIds != 0 && nTextures > 0 && pixels != 0); // Just in case...

	if (usedTextures == nTextures)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "No more textures!");
		id = BAD_TEXTURE_ID;
		return (false);
	}

	if (textureName != 0)
	{
		textureCacheMap::const_iterator it = textureCache.find(textureName);

		if (it != textureCache.end())
		{
			// Texture already created:
			id = (*it).second;
			return (true);
		}
	}

	// Create new texture:
	GLuint newTex = textureIds[usedTextures];
	++usedTextures;

	if (textureName != 0)
	{
		textureCache.insert(textureCacheMap::value_type(textureName, newTex));
	}

	glBindTexture(GL_TEXTURE_2D, newTex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);

	if (GLEW_SGIS_generate_mipmap) // Hardware mipmap generation:
	{
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
		glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);

		glTexImage2D(GL_TEXTURE_2D, 0, bytesPerPix, w, h, 0, glTexFmt, GL_UNSIGNED_BYTE, pixels);
	}
	else // No hardware mipmap generation available, use software:
	{
		gluBuild2DMipmaps(GL_TEXTURE_2D, bytesPerPix, w, h, glTexFmt, GL_UNSIGNED_BYTE, pixels);
	}

	CommLib::DbgPrintf(PRINT_MSG, "Created a %dx%d pixels texture", w, h);

	if (glGetError() != GL_NO_ERROR)
	{
		CommLib::DbgPrintf(PRINT_WARN, "OpenGL reported errors creating a 2D texture...");
	}

	id = newTex;

	return (true);
}

void CleanupTextures(void)
{
	if (textureIds != 0)
	{
		glDeleteTextures(nTextures, textureIds);

		usedTextures = 0;
		nTextures = 0;

		free(textureIds);
		textureIds = 0;
	}

	if (!textureCache.empty())
	{
		textureCache.clear();
	}

	CommLib::DbgPrintf(PRINT_MSG, "All textures released!");
}

Texture2D::Texture2D(const char * fileName, GLint wrapMode, GLint minFilter, GLint magFilter)
{
	if (!CreateGLTexture2DFromFile(fileName, wrapMode, minFilter, magFilter, this->width, this->height, this->id))
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to create 2D texture from file...");
		return;
	}

	this->wrapMode = wrapMode;
	this->minFilter = minFilter;
	this->magFilter = magFilter;
	this->fileName = fileName;
}

Texture2D::Texture2D(const unsigned char * pixels, const char * textureName,
					 int width, int height, int bytesPerPix, GLenum glTexFmt, GLint wrapMode, GLint minFilter, GLint magFilter)
{
	if (!CreateGLTexture2DFromMemory(pixels, textureName, wrapMode, minFilter, magFilter, glTexFmt, width, height, bytesPerPix, this->id))
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to create 2D texture from memory...");
		return;
	}

	this->width = width;
	this->height = height;
	this->wrapMode = wrapMode;
	this->minFilter = minFilter;
	this->magFilter = magFilter;

	if (textureName != 0)
	{
		this->fileName = textureName;
	}
}

bool Texture2D::Fail(void) const
{
	return (id == BAD_TEXTURE_ID);
}

void Texture2D::Bind(unsigned int texUnit) const
{
	assert(id != BAD_TEXTURE_ID);
	glActiveTexture(GL_TEXTURE0 + texUnit);
	glBindTexture(GL_TEXTURE_2D, id);
}

}; // namespace GfxLib {}